﻿using System.Runtime.Serialization;

namespace System.Collections.Generic
{
	public class CachedPool<T> : ISerializable, IDeserializationCallback
	{
		public delegate T TAllocator(CachedPool<T> pool);
		public const int DEFAULT_CAPACITY = 16;
		protected LinkedList<T> Cache;
		protected LinkedList<T> Pool;

		public int AllocCount
		{
			get
			{
				return Capacity + Count;
			}
		}

		public int Capacity
		{
			get
			{
				return Cache.Count;
			}
		}

		public int Count
		{
			get
			{
				return Pool.Count;
			}
		}

		public CachedPool<T>.TAllocator Allocator { get; set;}

		public LinkedListNode<T> First
		{
			get
			{
				return Pool.First;
			}
		}

		public LinkedListNode<T> Last
		{
			get
			{
				return Pool.Last;
			}
		}

		public CachedPool()
		{
			Cache = new LinkedList<T>();
			Pool = new LinkedList<T>();
		}

		public CachedPool(CachedPool<T>.TAllocator allocator)
		{
			Cache = new LinkedList<T>();
			Pool = new LinkedList<T>();
			Allocator = allocator;
		}

		public CachedPool(int capacity, CachedPool<T>.TAllocator allocator)
		{
			Cache = new LinkedList<T>();
			Pool = new LinkedList<T>();
			Allocator = allocator;
			Reserve(capacity);
		}

		public LinkedListNode<T> AllocAfter(LinkedListNode<T> node)
		{
			LinkedListNode<T> linkedListNode = PopCache();
			Pool.AddAfter(node, linkedListNode);

			return linkedListNode;
		}

		public LinkedListNode<T> AllocBefore(LinkedListNode<T> node)
		{
			LinkedListNode<T> linkedListNode = PopCache();
			Pool.AddBefore(node, linkedListNode);

			return linkedListNode;
		}

		public LinkedListNode<T> AllocFirst()
		{
			LinkedListNode<T> linkedListNode = PopCache();
			Pool.AddFirst(linkedListNode);

			return linkedListNode;
		}

		public LinkedListNode<T> AllocLast()
		{
			LinkedListNode<T> linkedListNode = PopCache();
			Pool.AddLast(linkedListNode);

			return linkedListNode;
		}

		public void Reserve(int reserveCount)
		{
			for (int i = 0; i < reserveCount; i++)
			{
				Cache.AddLast(new LinkedListNode<T>(Allocator(this)));
			}
		}

		protected LinkedListNode<T> PopCache()
		{
			LinkedListNode<T> last = Cache.Last;

			if (last == null)
			{
				int newCount = Count * 2;

				Reserve(newCount >= DEFAULT_CAPACITY ? newCount : DEFAULT_CAPACITY);
				last = Cache.Last;

				if (last == null)
				{
					throw new OutOfMemoryException("ERROR: CachedPool.PopCache() failed. internal error");
				}
			}

			Cache.Remove(last);

			return last;
		}

		public LinkedListNode<T> Find(T value)
		{
			return Pool.Find(value);
		}

		public LinkedListNode<T> Find(Predicate<T> match)
		{
			if (Pool.Count <= 0)
			{
				return null;
			}

			for (LinkedListNode<T> linkedListNode = Pool.First; linkedListNode != null; linkedListNode = linkedListNode.Next)
			{
				if (match(linkedListNode.Value))
				{
					return linkedListNode;
				}
			}

			return null;
		}

		public LinkedListNode<T> FindLast(T value)
		{
			return Pool.FindLast(value);
		}

		public LinkedListNode<T> FindLast(Predicate<T> match)
		{
			if (Pool.Count <= 0)
			{
				return null;
			}

			for (LinkedListNode<T> linkedListNode = Pool.Last; linkedListNode != null; linkedListNode = linkedListNode.Previous)
			{
				if (match(linkedListNode.Value))
				{
					return linkedListNode;
				}
			}

			return null;
		}

		public void Remove(LinkedListNode<T> node)
		{
			Pool.Remove(node);
			Cache.AddLast(node);
		}

		public bool Remove(T value)
		{
			LinkedListNode<T> linkedListNode = Pool.Find(value);

			if (linkedListNode == null)
			{
				return false;
			}

			Remove(linkedListNode);

			return true;
		}

		public void RemoveFirst()
		{
			LinkedListNode<T> first = Pool.First;
			Remove(first);
		}

		public void RemoveLast()
		{
			LinkedListNode<T> last = Pool.Last;
			Remove(last);
		}

		public void Clear()
		{
			for (int i = 0; i < Count; i++)
			{
				RemoveLast();
			}
		}

		public void Purge()
		{
			Pool.Clear();
			Cache.Clear();
		}

		public bool Contains(T value)
		{
			return null != Find(value);
		}

		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			Pool.GetObjectData(info, context);
		}

		public virtual void OnDeserialization(object sender)
		{
			Pool.OnDeserialization(sender);
		}
	}
}